﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
using Quick.EntityFrameworkCore;
using Quick.EntityFrameworkCore.Options;

namespace SampleModels;

public sealed class AutoConfiguration
{
    public static AutoConfigureDbContextOptionsBuilder Configure(IServiceCollection services)
    {
        return services.AutoConfigureDbContextOptions()
            .AddConnectionStringAccessor<ConnectionStringAccessor>()
            .AddProviderNameAccessor<NameAccessor>()
            .AddDatabaseSupport<SqliteAutoConfigure>()
            .AddDatabaseSupport<NpgsqlAutoConfigure>();
    }

    public class NpgsqlAccessor : IProviderNameAccessor
    {
        public object Get() => Providers.Npgsql;
    }

    public class NameAccessor : IProviderNameAccessor
    {
        protected virtual IConfiguration Configuration { get; set; }
        protected virtual ILogger _logger { get; set; }
        public NameAccessor(IConfiguration configuration,
            ILogger<NameAccessor> logger)
        {
            Configuration = configuration;
            _logger = logger;
        }

        protected string? _name => Configuration.GetSection("DatabaseProvider").Value;

        public object Get()
        {
            _logger?.LogDebug("DatabaseProvider: {Value}", _name);

            var value = _name switch
            {
                "sqlite" => Providers.Sqlite,
                "postgres" => Providers.Npgsql,
                _ => throw new NotSupportedException(_name)
            };

            return value;
        }
    }

    public class Providers
    {
        public const string Sqlite = nameof(Sqlite);
        public const string Npgsql = nameof(Npgsql);
        public const string SqlServer = nameof(SqlServer);
    }

    public class ConnectionStringAccessor : IConnectionStringAccessor
    {
        protected virtual IConfiguration Configuration { get; set; }
        protected virtual ILogger _logger { get; set; }
        public ConnectionStringAccessor(IConfiguration configuration, 
            ILogger<ConnectionStringAccessor> logger)
        {
            Configuration = configuration;
            _logger = logger;
        }

        public string Get()
        {
            var connectionName = Configuration.GetSection("ConnectionName").Value ?? "Default";
            _logger?.LogDebug("Connection Name: {Value}", connectionName);

            var connectionString = Configuration.GetConnectionString(connectionName) ?? string.Empty;
            _logger?.LogDebug("Connection String: {Value}", connectionString);

            return connectionString;
        }
    }

    public class SqliteAutoConfigure : IEntityFrameworkCoreDatabaseOptionsProvider
    {
        public string Name => Providers.Sqlite;

        public void Configure<TDbContext>(IServiceProvider serviceProvider, DbContextOptionsBuilder builder) where TDbContext : DbContext
        {
            var accessor = serviceProvider.GetRequiredService<IConnectionStringAccessor>();
            var optionsActions = serviceProvider.GetServices<IDatabaseProviderOptions<SqliteDbContextOptionsBuilder>>();
            var dbContextOptionsActions = serviceProvider.GetServices<IDatabaseProviderOptions<TDbContext, SqliteDbContextOptionsBuilder>>();

            builder.UseSqlite(accessor.Get(), (options) =>
            {
                if (optionsActions is not null && optionsActions.Any())
                {
                    foreach (var action in optionsActions)
                    {
                        action.Configure(options);
                    }
                }

                if (dbContextOptionsActions is not null && dbContextOptionsActions.Any())
                {
                    foreach (var dbAction in dbContextOptionsActions)
                    {
                        dbAction.Configure(options);
                    }
                }
            });
        }
    }

    public class NpgsqlAutoConfigure : IEntityFrameworkCoreDatabaseOptionsProvider
    {
        public string Name => Providers.Npgsql;

        public void Configure<TDbContext>(IServiceProvider serviceProvider, DbContextOptionsBuilder builder) where TDbContext : DbContext
        {
            var accessor = serviceProvider.GetRequiredService<IConnectionStringAccessor>();
            var optionsActions = serviceProvider.GetServices<IDatabaseProviderOptions<NpgsqlDbContextOptionsBuilder>>();
            var dbContextOptionsActions = serviceProvider.GetServices<IDatabaseProviderOptions<TDbContext, NpgsqlDbContextOptionsBuilder>>();

            builder.UseNpgsql(accessor.Get(), (options) =>
            {
                if (optionsActions is not null && optionsActions.Any())
                {
                    foreach (var action in optionsActions)
                    {
                        action.Configure(options);
                    }
                }

                if (dbContextOptionsActions is not null && dbContextOptionsActions.Any())
                {
                    foreach (var dbAction in dbContextOptionsActions)
                    {
                        dbAction.Configure(options);
                    }
                }
            });
        }
    }
}
